数据绑定
数据绑定允许你将代码与 Rive 编辑器中绑定的元素连接起来。通过使用 View Model,你可以在编辑器中定义可绑定属性,然后在运行时通过 React Hook 读取和写入这些属性的值。
View Model
使用 useViewModel Hook 获取 View Model 的引用。你需要传入从 useRive 获取的 rive 对象。
import { useRive, useViewModel } from '@rive-app/react-webgl2';
const { rive, RiveComponent } = useRive({
src: 'your_file.riv',
// ... 其他选项
});
// 方式 1:获取画板的默认 ViewModel
const defaultViewModel = useViewModel(rive);
// 方式 2:显式获取默认 ViewModel
const defaultViewModelExplicit = useViewModel(rive, { useDefault: true });
// 方式 3:按名称获取 ViewModel
const namedViewModel = useViewModel(rive, { name: 'MyViewModelName' });
View Model 实例
使用 useViewModelInstance Hook 从 useViewModel Hook 返回的 View Model 创建 View Model 实例。
import { useRive, useViewModel, useViewModelInstance } from '@rive-app/react-webgl2';
const { rive, RiveComponent } = useRive({
src: 'your_file.riv',
artboard: 'MyArtboard',
stateMachine: 'MyStateMachine',
// ... 其他选项
});
const viewModel = useViewModel(rive, { name: 'MyViewModelName' });
// 或:const viewModel = useViewModel(rive); // 默认 VM
// 获取默认实例,不绑定
const defaultUnbound = useViewModelInstance(viewModel, { useDefault: true });
// 获取命名实例,不绑定
const namedUnbound = useViewModelInstance(viewModel, { name: 'MyInstanceName' });
// 创建新的空白实例,不绑定
const newUnbound = useViewModelInstance(viewModel, { useNew: true });
你也可以通过向 useViewModelInstance Hook 传入 rive 对象,将 View Model 实例直接绑定到 Rive 实例。
import { useRive, useViewModel, useViewModelInstance } from '@rive-app/react-webgl2';
const { rive, RiveComponent } = useRive({
src: 'your_file.riv',
artboard: 'MyArtboard',
stateMachine: 'MyStateMachine',
autoBind: false, // 禁用自动绑定,以便稍后手动绑定
// ... 其他选项
});
const viewModel = useViewModel(rive, { name: 'MyViewModelName' });
// 获取默认实例(隐式)并绑定
const defaultBound = useViewModelInstance(viewModel, { rive });
// 获取命名实例并绑定
const namedBound = useViewModelInstance(viewModel, { name: 'MyInstanceName', rive });
// 创建新的空白实例并绑定
const newBound = useViewModelInstance(viewModel, { useNew: true, rive });
如果你在 useRive 中设置了 autoBind: true,一旦 Rive 加载完成,你可以直接通过 rive.viewModelInstance 访问自动绑定的默认实例,无需使用 useViewModel 或 useViewModelInstance。
const { rive, RiveComponent } = useRive({
src: 'your_file.riv',
artboard: 'MyArtboard',
stateMachine: 'MyStateMachine',
autoBind: true,
});
// 加载完成后,实例即可用:
const boundInstance = rive?.viewModelInstance;
绑定
对于 React,无需额外步骤即可将 View Model 实例绑定到 Rive 组件。向 useViewModelInstance 传入 rive 对象会自动处理绑定——该 Hook 会设置实例并调度一次 bind()。
先设置所有实例,再调用一次 bind()。 设置实例的开销很小;bind() 的开销要大得多,每次按实例调用都会重复支付该开销。批量处理在初始化时最为重要,尤其是在有许多绑定或深层嵌套画板的图形上。
View Model Hook 已为你处理了这一点:在同一渲染提交中解析的 Hook 共享一次合并的 bind()。当手动绑定时——例如在 onRiveReady 中——使用 setViewModelInstance() / setGlobalViewModelInstance() 逐个设置实例,然后用一次 bind() 统一应用。
首帧前绑定(Binding before the first frame)
View Model Hook 在 Rive 加载后运行,这意味着即使设置了 autoplay: false,第一帧也会在它们绑定之前已经推进和渲染。如果想在首次渲染前设置数据,请在 useRive 的 onRiveReady 回调中进行数据绑定设置。它在文件加载后、状态机推进之前同步接收 Rive 实例,因此你可以设置实例和属性值,让第一帧就使用这些值进行渲染。
import { useRive, ViewModel, ViewModelInstance } from '@rive-app/react-webgl2';
const { RiveComponent } = useRive({
src: 'your_file.riv',
stateMachine: 'MyStateMachine',
autoplay: true,
autoBind: false, // 自行在首帧前设置实例
onRiveReady: (rive) => {
const mainVM = rive.viewModelByName('MyViewModel') as ViewModel;
const instance = mainVM.defaultInstance() as ViewModelInstance;
instance.string('username').value = 'Rive';
rive.setViewModelInstance(instance);
rive.bind(); // 应用已设置的实例
},
});
对于任何预渲染数据绑定工作,请使用 onRiveReady 而非 onLoad。你可以从 Rive 实例访问的所有内容——包括全局视图模型——都可以在其中准备。
自动绑定
const { rive, RiveComponent } = useRive({
src: 'your_file.riv',
artboard: 'MyArtboard',
stateMachine: 'MyStateMachine',
autoBind: true, // 启用自动绑定
// ... 其他选项
});
// 加载完成后,主视图模型实例可用:
const boundInstance = rive?.viewModelInstance;
全局视图模型(Global View Models)
在编辑器中标记为全局的视图模型在整个文 件中共享,而不是由单个画板拥有。文件为每个全局视图模型持有一个按名称寻址的槽位,因此每个画板和状态机都读取同一个实例——适用于主题、会话或设置等应用级状态。
使用 useGlobalViewModelInstance Hook 来解析实例并将其注册到全局名称下。它接收视图模型(来自 useViewModel)、全局槽位名称以及与 useViewModelInstance 相同类型的选择器参数:
import {
useRive,
useViewModel,
useGlobalViewModelInstance,
useViewModelInstanceColor,
} from '@rive-app/react-webgl2';
const { rive, RiveComponent } = useRive({
src: 'your_file.riv',
stateMachine: 'State Machine 1',
autoBind: false,
});
const themeViewModel = useViewModel(rive, { name: 'Theme' });
// 解析默认实例并将其注册为 "Theme" 全局
const themeInstance = useGlobalViewModelInstance(themeViewModel, 'Theme', { rive });
// 属性 Hook 的用法与主实例完全相同
const { value: bgColor, setValue: setBgColor } = useViewModelInstanceColor(
'backgroundColor',
themeInstance
);
第三个参数选择要解析的实例:
{ rive }— 视图模型的默认实例(隐式默认){ instanceName: 'Dark', rive }— 具有该名称的实例{ useNew: true, rive }— 新的空白实例{ instance: myInstance, rive }— 你自己创建的实例
传入 rive 会将实例注册为全局并调度绑定。省略它则只解析实例而不绑定。
你只需要为想要读取或写入属性的全局视图模型使用 Hook。绑定会用文件中该视图模型的默认实例填充你未设置的每个槽位——包括主视图模型实例。
要通过代码列出文件的全局视图模型或读取当前占用某个槽位的实例,直接通过 rive 实例操作:
const names = rive?.globalViewModelNames(); // 例如 ["Theme", "Session"]
const themeInstance = rive?.globalViewModelInstance('Theme'); // ViewModelInstance | null
globalViewModelInstance() 不会创建实例——在 Hook 设置、在 onRiveReady 中设置或通过 autoBind: true 创建之前,它返回 null。
属性
列出属性
// 从 useViewModel 返回的 View Model 访问属性
const viewModel = useViewModel(rive);
console.log(viewModel?.properties); // [{ name: "status", type: "enum", enumName: "Status" }, ...]
每个条目都有一个 name 和一个 type。类型为 "enum" 的属性还带有 enumName,你可以将其与 rive.enums() 的结果匹配,以查找该枚举的可用值。
读取和写入属性
使用特定属性类型的 Hook 来获取和设置属性值。
useViewModelInstanceBoolean:读取/写入布尔属性useViewModelInstanceString:读取/写入字符串属性useViewModelInstanceNumber:读取/写入数字属性useViewModelInstanceColor:读取/写入颜色属性,附带额外的 RGB/alpha 方法useViewModelInstanceEnum:读取/写入枚举属性,附带可用值列表useViewModelInstanceTrigger: 触发事件,可选回调
这些 Hook 返回当前的 value 和用于更新它的函数(setValue、setRgb、trigger)。如果属性未找到或 Hook 接收了无效的 viewModelInstance,value 将为 null。
import {
useViewModelInstanceBoolean,
useViewModelInstanceString,
useViewModelInstanceNumber,
useViewModelInstanceEnum,
useViewModelInstanceColor,
useViewModelInstanceTrigger
} from '@rive-app/react-webgl2';
// 假设 viewModelInstance 已通过 useViewModelInstance 或 rive.viewModelInstance 获取
// 布尔
const { value: isActive, setValue: setIsActive } = useViewModelInstanceBoolean(
'isToggleOn', // 属性路径
viewModelInstance
);
// 设置:setIsActive(true);
// 字符串
const { value: userName, setValue: setUserName } = useViewModelInstanceString(
'user/name', // 属性路径
viewModelInstance
);
// 设置:setUserName('Rive');
// 数字
const { value: score, setValue: setScore } = useViewModelInstanceNumber(
'levelScore', // 属性路径
viewModelInstance
);
// 设置:setScore(100);
// 枚举
const { value: status, setValue: setStatus, values: statusOptions } = useViewModelInstanceEnum(
'appStatus', // 属性路径
viewModelInstance
);
// 设置:setStatus('loading');
// 获取可用选项:statusOptions 是类似 ['idle', 'loading', 'error'] 的数组
// 颜色
const {
value: themeColor, // 原始数值,如 -3267805
setRgb: setThemeColorRgb, // 设置 RGB 分量(0-255 值)
setAlpha: setThemeColorAlpha, // 设置 Alpha 分量(0-255)
setOpacity: setThemeColorOpacity, // 设置不透明度(0.0-1.0)
setRgba: setThemeColorRgba, // 一次性设置所有分量
setValue: setThemeColorValue // 设置原始颜色值
} = useViewModelInstanceColor(
'ui/themeColor', // 属性路径
viewModelInstance
);
// 设置 RGB:setThemeColorRgb(0, 128, 255); // 设置为蓝色
// 设置 Alpha:setThemeColorAlpha(128); // 设置为 50% 不透明度
// 设置不透明度:setThemeColorOpacity(0.5); // 设置为 50% 不透明度
// 设置 RGBA:setThemeColorRgba(0, 128, 255, 255); // 蓝色,完全不透明
// 设置值:setThemeColorValue(-3267805); // 使用原始颜色值
// 触发器(无值,仅有触发函数)
const { trigger: playEffect } = useViewModelInstanceTrigger(
'playButtonEffect', // 属性路径
viewModelInstance,
{
// 可选回调,在触发器触发时调用
onTrigger: () => {
console.log('触发器已触发!');
}
}
);
// 触发:playEffect();
当 Rive 图形中的属性发生变化时,每个 Hook 返回的 value 会自动更新。
嵌套属性路径
通过将完整路径(以 / 分隔)作为属性 Hook 的第一个参数来访问嵌套属性。
import { useViewModelInstanceString, useViewModelInstanceNumber } from '@rive-app/react-webgl2';
// 访问 'settings/theme/name'(字符串)
const { value: themeName, setValue: setThemeName } = useViewModelInstanceString(
'settings/theme/name',
viewModelInstance
);
// 访问 'settings/volume'(数字)
const { value: volume, setValue: setVolume } = useViewModelInstanceNumber(
'settings/volume',
viewModelInstance
);
console.log('当前主题:', themeName);
// setThemeName('Dark Mode');
// setVolume(80);
可观察性
React Hook 自动处理可观察性。当属性值在 Rive 实例中发生变化时(无论是通过 Hook 设置还是由于内部绑定),相应 Hook(如 useViewModelInstanceString)返回的 value 会更新。此状态变化会触发 React 组件的重新渲染,使你能够响应新值。
对于触发器,你可以直接向 useViewModelInstanceTrigger Hook 提供 onTrigger 回调,该回调在 Rive 实例中激活触发器时触发。
import { useViewModelInstanceTrigger } from '@rive-app/react-webgl2';
// 假设 viewModelInstance 可用
const { trigger } = useViewModelInstanceTrigger(
'showPopup',
viewModelInstance,
{
onTrigger: () => {
console.log('显示弹窗触发器已触发!');
// 显示你的弹窗 UI
}
}
);
图片
使用 useViewModelInstanceImage Hook 设置 View Model 实例上的图片属性。
import { useRive, useViewModel, useViewModelInstance, useViewModelInstanceImage } from '@rive-app/react-webgl2';
const { rive, RiveComponent } = useRive({
src: 'your_file.riv',
artboard: 'MyArtboard',
stateMachine: 'MyStateMachine',
autoBind: false,
// ... 其他选项
});
const viewModel = useViewModel(rive, { name: 'MyViewModel' });
const viewModelInstance = useViewModelInstance(viewModel, { rive });
// 获取图片属性设置器
const { setValue: setImage } = useViewModelInstanceImage(
'profileImage', // 属性路径
viewModelInstance
);
// 加载并设置随机图片
const loadRandomImage = async () => {
if (!setImage) return;
try {
const imageUrl = 'https://picsum.photos/300/500';
const response = await fetch(imageUrl);
const imageBuffer = await response.arrayBuffer();
// 从响应中解码图片
const decodedImage = await decodeImage(new Uint8Array(imageBuffer));
setImage(decodedImage);
// 清理解码后的图片
decodedImage.unref();
} catch (error) {
console.error('加载图片失败:', error);
}
};
// 清除图片
const clearImage = () => {
if (setImage) {
setImage(null);
}
};
字体(Fonts)
字体属性允许你在运行时替换绑定文本使用的字体。解码字体字节并将结果分配给属性的 value。
const { setValue: setHeadingFont } = useViewModelInstanceFont(
"headingFont",
rive?.viewModelInstance
);
useEffect(() => {
if (setHeadingFont) {
loadFont("Orbitron.ttf");
}
}, [setHeadingFont]);
const loadFont = async (url: string) => {
try {
const response = await fetch(url);
const fontBuffer = await response.arrayBuffer();
const decodedFont = await decodeFont(new Uint8Array(fontBuffer));
setHeadingFont(decodedFont);
decodedFont.unref();
} catch (error) {
console.error("加载字体失败:", error);
}
};
dataBindingFonts列表
使用 useViewModelInstanceList Hook 管理 View Model 实例上的列表属性。
import { useRive, useViewModel, useViewModelInstance, useViewModelInstanceList } from '@rive-app/react-webgl2';
const { rive, RiveComponent } = useRive({
src: 'your_file.riv',
artboard: 'MyArtboard',
stateMachine: 'MyStateMachine',
autoBind: false,
// ... 其他选项
});
const viewModel = useViewModel(rive, { name: 'MyViewModel' });
const viewModelInstance = useViewModelInstance(viewModel, { rive });
// 获取列表属性及操作函数
const {
length,
addInstance,
addInstanceAt,
removeInstance,
removeInstanceAt,
getInstanceAt,
swap
} = useViewModelInstanceList('todos', viewModelInstance);
// 添加新的待办事项
const handleAddItem = () => {
const todoItemViewModel = rive?.viewModelByName?.('TodoItem');
if (todoItemViewModel) {
const newTodoItem = todoItemViewModel.instance?.();
if (newTodoItem) {
// 设置一些初始值
newTodoItem.string('description').value = '购买食品杂货';
addInstance(newTodoItem);
}
}
};
// 在指定位置插入项目
const handleInsertItem = () => {
const todoItemViewModel = rive?.viewModelByName?.('TodoItem');
if (todoItemViewModel) {
const newTodoItem = todoItemViewModel.instance?.();
if (newTodoItem) {
addInstanceAt(newTodoItem, 0); // 插入到开头
}
}
};
// 按实例移除第一个项目
const handleRemoveFirst = () => {
const firstInstance = getInstanceAt(0);
if (firstInstance) {
removeInstance(firstInstance);
}
};
// 按索引移除项目
const handleRemoveAt = () => {
if (length > 0) {
removeInstanceAt(0);
}
};
// 交换两个项目
const handleSwap = () => {
if (length >= 2) {
swap(0, 1);
}
};
console.log(`列表共有 ${length} 项`);
画板
使用 useViewModelInstanceArtboard Hook 设置 View Model 实例上的画板属性。
import { useRive, useViewModel, useViewModelInstance, useViewModelInstanceArtboard } from '@rive-app/react-webgl2';
const { rive, RiveComponent } = useRive({
src: 'your_file.riv',
artboard: 'MyArtboard',
stateMachine: 'MyStateMachine',
autoBind: true,
// ... 其他选项
});
// 获取画板属性设置器
const { setValue: setArtboard1 } = useViewModelInstanceArtboard(
'artboard_1', // 属性路径
rive?.viewModelInstance
);
const { setValue: setArtboard2 } = useViewModelInstanceArtboard(
'artboard_2', // 属性路径
rive?.viewModelInstance
);
// 从同一文件中分配不同的画板
const handleSetBlueArtboard = () => {
if (rive) {
const blueArtboard = rive.getBindableArtboard('ArtboardBlue');
setArtboard1(blueArtboard);
}
};
const handleSetRedArtboard = () => {
if (rive) {
const redArtboard = rive.getBindableArtboard('ArtboardRed');
setArtboard2(redArtboard);
}
};
const handleSetGreenArtboard = () => {
if (rive) {
const greenArtboard = rive.getBindableArtboard('ArtboardGreen');
setArtboard1(greenArtboard);
}
};
枚举
const { rive } = useRive({
src: 'your_file.riv',
artboard: 'MyArtboard',
stateMachine: 'MyStateMachine',
autoBind: true
// ... 其他选项
});
const enums = rive?.enums();
console.log(enums);
示例
请参阅 Rive React 仓库 中的 DataBinding 故事以获取演示。